<!DOCTYPE html>
<html>
  <head>
    <!-- [[! Document Settings ]] -->
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />

    <!-- [[! Page Meta ]] -->
    <title>Android 生命周期架构组件与 RxJava 完美协作</title>
    <meta name="description" content="在编码 - " />

    <meta name="HandheldFriendly" content="True" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="shortcut icon" href="/assets/images/favicon.ico" >

    <!-- [[! Styles'n'Scripts ]] -->
    <link rel="stylesheet" type="text/css" href="/assets/css/screen.css" />
    <link rel="stylesheet" type="text/css"
      href="//fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400" />

    <link rel="stylesheet" type="text/css" href="/assets/css/prism.css" />

    <!-- [[! Ghost outputs important style and meta data with this tag ]] -->
    <link rel="canonical" href="/" />
<meta name="referrer" content="origin" />
<link rel="next" href="/page2/" />

<meta property="og:site_name" content="在编码" />
<meta property="og:type" content="website" />
<meta property="og:title" content="在编码" />
<meta property="og:description" content="" />
<meta property="og:url" content="/" />
<meta property="og:image" content="/assets/images/cover1.jpg" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="在编码" />
<meta name="twitter:description" content="" />
<meta name="twitter:url" content="/" />
<meta name="twitter:image:src" content="/assets/images/cover1.jpg" />

<script type="application/ld+json">
  {
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "Finding The Way Home",
  "url": "/",
  "image": "/assets/images/cover1.jpg",
  "description": ""
  }
</script>

<meta name="generator" content="Jekyll 3.0.0" />
<link rel="alternate" type="application/rss+xml" title="在编码" href="/feed.xml" />


  </head>
  <body class="home-template nav-closed">

    <div class="nav">
  <h3 class="nav-title">Home</h3>
  <a href="#" class="nav-close">
    <span class="hidden">Close</span>
  </a>
  <ul>
    <li class="nav-home " role="presentation"><a href="/">Home</a></li>
    <!-- <li class="nav-about " role="presentation"><a href="/about">About</a></li>
    <li class="nav-author " role="presentation"><a href="/author/casper">Author</a></li> -->
    <li class="nav-ios " role="presentation"><a href="/tag/iOS">iOS</a></li>
    <li class="nav-mvc " role="presentation"><a href="/tag/Android">Android</a></li>
    <li class="nav-server " role="presentation"><a href="/tag/Server">Server</a></li>
  </ul>
  <a class="subscribe-button icon-feed" href="/feed.xml">Subscribe</a>
</div>
<span class="nav-cover"></span>


    <div class="site-wrapper">

      <!-- [[! Everything else gets inserted here ]] -->
      <!-- < default -->

<!-- The comment above "< default" means - insert everything in this file into -->
<!-- the [body] of the default.hbs template, which contains our header/footer. -->

<!-- Everything inside the #post tags pulls data from the post -->
<!-- #post -->

<header class="main-header post-head no-cover">
  <nav class="main-nav overlay clearfix">
      <a class="back-button icon-arrow-left" href='/'>Home</a>
    <a class="menu-button icon-menu" href="#"><span class="word">Menu</span></a>
  </nav>
</header>

<main class="content" role="main">

  <article class="post">

    <header class="post-header">
      <h1 class="post-title">Android 生命周期架构组件与 RxJava 完美协作</h1>
      <section class="post-meta">
        <a href='/'>Listen</a>
        <time class="post-date" datetime="2017-08-05">05 Aug 2017</time>
        <!-- [[tags prefix=" on "]] -->
        
        on
        
        
        <a href='/tag/Android'>Android</a>
        
        
        
      </section>
    </header>

    <section class="post-content">

      <p>本文主要讲述了 Android 生命周期架构组件如何与 RxJava 协作，从此过上了幸福生活的故事。 </p>

<p>涉及的内容有： </p>

<ul>
<li>Android 生命周期架构组件</li>
<li>LiveData 有哪些惊艳特性</li>
<li>如何使用 RxJava 中的 Observable 代替 LiveData，同时拥有它的那些惊艳特性</li>
<li>Android 架构组件中的 ViewModel</li>
<li>如何使用 RxJava 优雅地实现 ViewModel </li>
</ul>

<blockquote>
<p>2017-10 月更新：更新到 Support Library 26.1+，建议在 PC 下阅读</p>
</blockquote>

<h2>Handling Lifecycles</h2>

<p>Android 的生命周期自上古时代以来就是个噩梦般的存在，很多难以察觉，莫名其妙的 BUG 就与之相关。处理不好，很容易导致内存泄漏和应用崩溃。譬如在 Activity 状态保存（Activity 的 onSaveInstanceState 被框架调用）后执行 Fragment 事务，将会导致崩溃。</p>

<p>谷歌官方推出的生命周期 <a href="https://developer.android.com/topic/libraries/architecture/index.html">架构组件</a>，似乎终于能让我们睡个好觉了。 现在 Support Library 26.1+ 已经集成了这个生命周期组件。 </p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy">buildscript {
    repositories {
        jcenter()
        maven { url &#39;https://maven.google.com&#39; }
    }
}
</code></pre></figure><figure class="highlight"><pre><code class="language-groovy" data-lang="groovy">dependencies {
    //  using Support Library 26.1+
    compile &#39;com.android.support:appcompat-v7:26.1.0&#39;
    compile &#39;com.android.support:support-v4:26.1.0&#39;
    compile &#39;com.android.support:design:26.1.0&#39;
}
</code></pre></figure>
<p><a href="https://developer.android.com/reference/android/arch/lifecycle/package-summary.html">android.arch.lifecycle</a> 开发包提供的类和接口，能让我们构建生命周期感知（<em>lifecycle-aware</em>）组件 —— 这些组件可以基于 Activity 或 Fragment 的当前生命周期自动调整它们的行为。</p>

<p><img src="../images/android/lifecycle-classes.png" alt=""> </p>

<p>从图中我们可以看到 LifecycleOwner 是 Lifecycle 的持有者，通常是一个 Activity 或 Fragment。想要获取 Lifecycle 只能通过 LifecycleOwner 的 getLifecycle 方法。Lifecycle 是可观察的，它可以持有多个 LifecycleObserver。 </p>

<h3>Lifecycle</h3>

<p>可以看到，<a href="https://developer.android.com/reference/android/arch/lifecycle/Lifecycle.html">Lifecycle</a> 是整个宇宙的核心。 </p>

<p>Lifecycle 内部维护了两个枚举，一叫 Event，另一个叫 State。 </p>

<p><img src="../images/android/lifecycle-states.png" alt=""> 
Event 是对 Android 组件（Activity 或 Fragment）生命周期函数的映射，当 Android Framework 回调生命周期函数时，Lifecycle 会检查当前事件和上一个事件是否一致，如果不一致，就根据当前事件计算出当前状态，并通知它的观察者（LifecycleObserver）生命状态已经发生变化。观察者收到通知后，通过 Lifecycle 提供的 getCurrentSate 方法来获取刚刚 Lifecycle 计算出来的当前状态，观察者根据这个状态来决定要不要执行某些操作。</p>

<h3>LifecycleOwner</h3>

<p><a href="https://developer.android.com/reference/android/arch/lifecycle/LifecycleOwner.html">LifecycleOwner</a> 是个接口，只有一个 getLifecycle() 方法。</p>

<p>它从个体类（例如，Activity 和 Fragment）中抽象出生命周期所有权。这样，我们编写的生命周期感知组件，就可以在任何遵循了 LifecycleOwner 协议的类中使用，而不需要管它是 Activity 还是 Fragment。</p>

<p>版本 26.1+ 支持包中的 AppCompatActivity 或 Fragment 已经直接或间接地实现了 LifecycleOwner。当然，你也可以编写自己的 LifecycleOwner。</p>

<h3>LifecycleObserver</h3>

<p>那些可以使用 Lifecycle 的类被称为生命周期感知组件。谷歌提倡那些需要和 Android 生命周期协作的类库提供生命周期感知组件，这样客户端代码就可以很容易集成这些类库，而不需要客户端手动管理类库中和 Android 生命周期相关的代码。</p>

<p>实现 LifecycleObserver 的类便是生命周期感知组件。实现起来也并不复杂。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public class MyObserver implements LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void onResume() {
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void onPause() {
    }
}
owner.getLifecycle().addObserver(new MyObserver());

</code></pre></figure>
<p>打个注解就可以监听对应的生命周期事件了，别忘了调用 owner.getLifecycle().addObserver() 把观察者注册到 Lifecycle.</p>

<h3>LiveData</h3>

<p>终于，我们惊艳的主角登场了。 </p>

<p><a href="https://developer.android.com/topic/libraries/architecture/livedata.html">LiveData</a> 是一个 observable 数据的持有类，和普通的 observable 不同，LiveData 是生命周期感知的，意味着它代表其它应用组件譬如 Activity、Fragment、Service 的生命周期。这确保了 LiveData 只会更新处于活跃状态的组件。</p>

<p>LiveData 通过内部类的形式实现了 LifecycleObserver，它整个工作流程大概是这样的：</p>

<ol>
<li>将实现了 LifecycleObserver 的内部类注册到 owner 的 Lifecycle。</li>
<li>LifecycleObserver 监听了 Lifecycle 所有的生命周期事件</li>
<li>当有生命周期事件发生时，检查 Lifecycle 的状态是否至少是 <code>STARTED</code> 来判断 lifecycle 是否处于活跃状态</li>
<li>当维护的值被改变时，如果 Lifecycle 处于活跃状态，通知观察者(实现了 <code>android.arch.lifecycle.Observer</code> 接口的对象)，否则什么也不做</li>
<li>当 Lifecycle 从非活跃状态恢复到活跃状态时，检查维护的值是否在非活跃期间有更新过，如果有，通知观察者</li>
<li>当 Lifecycle 处于完结状态 <code>DESTROYED</code> 时，从 Lifecycle 中移除 LifecycleObserver。 </li>
</ol>

<p>很显然，LiveData 是响应式的，令人惊艳的是，它有效解决了后台回调和 Android 生命周期的上古谜题。 </p>

<p>LiveData 使用起来是这样的： </p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public class MyViewModel extends ViewModel {
    private MutableLiveData&lt;List&lt;User&gt;&gt; users;
    public LiveData&lt;List&lt;User&gt;&gt; getUsers() {
        if (users == null) {
            users = new MutableLiveData&lt;List&lt;Users&gt;&gt;();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // do async operation to fetch users
    }
}
</code></pre></figure>
<p>现在，Activity 可以访问这个列表如下：</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -&gt; {
            // update UI
        });
    }
}
</code></pre></figure>
<p>它还提供了 Transformations 来帮助我们转换 LiveData 所持有的数据。 </p>

<p>Transformations.map() </p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">LiveData&lt;User&gt; userLiveData = ...;
LiveData&lt;String&gt; userName = Transformations.map(userLiveData, user -&gt; {
    user.name + &quot; &quot; + user.lastName
});
</code></pre></figure>
<p>Transformations.switchMap() </p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">private LiveData&lt;User&gt; getUser(String id) {
  ...;
}

LiveData&lt;String&gt; userId = ...;
LiveData&lt;User&gt; user = Transformations.switchMap(userId, id -&gt; getUser(id) );
</code></pre></figure>
<p>熟悉 RxJava 的小伙伴，是不是觉得少了些什么？</p>

<p>在处理数据和业务上，LiveData 远远没有 RxJava 强大，然而 LiveData 在 Android 生命周期的表现上也确实令人惊艳。有没有什么办法将两者合一？ </p>

<p>谷歌为我们提供了 <a href="https://developer.android.com/reference/android/arch/lifecycle/LiveDataReactiveStreams.html">LiveDataReactiveStreams</a> 来在 LiveData 和 RxJava 之间切换。不过没什么用。</p>

<h3>Live</h3>

<p>有没有办法能让 RxJava 像 LiveData 那样感知 Android 生命周期，拥有 LiveData 那些优点？ </p>

<p>LiveData 以内部类的方式实现了 LifecycleObserver， RxJava 通过 ObservableTransformer 的方式实现 LifecycleObserver。</p>

<p>这正是 <a href="https://github.com/listenzz/Live">Live</a> 的由来。至此，令人惊艳的 LiveData 完成了它的使命。</p>

<p>Live 使用起来是这样的</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">protected void onCreate(Bundle savedInstanceState) {
    mObservable
            .compose(Live.bindLifecycle(this))
            .subscribe();
}
</code></pre></figure>
<p>没有回调，只有优雅的流。 </p>

<p>熟悉 <a href="https://github.com/trello/RxLifecycle">RxLifecycle</a> 的小伙伴会发现，Live 和 RxLifecycle 不仅使用方式一致，而且功能似乎也一样。 </p>

<p>不，它们有各自的使用场景。</p>

<p><a href="https://github.com/listenzz/Live">Live</a> 像 LiveData 那样，在页面不活跃时不会投递数据，当页面重新活跃，补投最新的数据，如果有的话。 Live 只会在 Lifecycle 发出 DESTROYED 事件时终结整个流。 总之 Live 拥有像 LiveData 那样的生命周期感知。</p>

<p>RxLifecycle 无论何时，只要流还没有终结，都会投递数据。RxLifecycle 可以指定在特定的生命周期事件发生时终结流，例如在 onStop 时。</p>

<p>我们来看看 Live 自带的 demo。</p>

<p><img src="../images/android/live-1.png" alt=""></p>

<p>我们会以 startActivityForResult 的方式开启 SecondActivity </p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivityForResult(intent, 100);
            }
        });
</code></pre></figure>
<p><img src="../images/android/live-2.png" alt=""> </p>

<p>SecondActivity 上有个计时器，当按 Home 键退到后台时，日志停止打印，当重新回到前台时，日志又重新打印。 而流从来没有结束过。像 LiveData 那样惊艳。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">Observable.interval(1, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .compose(Live.&lt;Long&gt;bindLifecycle(this))
                .subscribe(new Consumer&lt;Long&gt;() {
                    @Override
                    public void accept(@NonNull Long aLong) throws Exception {
                        Log.w(TAG, String.valueOf(aLong));
                        textView.setText(String.valueOf(aLong));
                    }
                });
</code></pre></figure>
<p><img src="../images/android/live-3.jpg" alt=""> </p>

<p>按下 SecondActivity 上的返回按钮时，调用 <code>setResult(RESULT_OK)</code> ，返回 MainActivity。MainActivity 在 <code>onActivityResult</code> 中模拟了数据更新，这个更新要求 MainActivity 显示一个 Fragment。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    subject.compose(Live.&lt;String&gt;bindLifecycle(this))
            .subscribe(new Consumer&lt;String&gt;() {
                @Override
                public void accept(@NonNull String s) throws Exception {
                    showFragment();
                }
            });
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 100 &amp;&amp; resultCode == Activity.RESULT_OK) {
        subject.onNext(&quot;show fragment&quot;);
    }
}
</code></pre></figure>
<p><img src="../images/android/live-4.png" alt=""> </p>

<p>在以往，如果处理不好，这个操作会导致应用崩溃。</p>
<figure class="highlight"><pre><code class="language-console" data-lang="console">java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState 
</code></pre></figure>
<p>但是现在，噩梦已经过去。 <a href="https://github.com/listenzz/Live">Live</a> 让生活更美好。</p>

<h2>ViewModel</h2>

<p>故事大概讲了一半。让我们继续。 </p>

<p>看到 ViewModel 这个词，就会让人想起 MVVM，ViewModel 正是 MVVM 架构里面的一个重要角色。</p>

<p>MVVM 按职责把类分为三种角色，分别是 View，ViewModel，Model。ViewModel 正好处于中间，是连接 View 和 Model 的桥梁。</p>

<ul>
<li>View 只做两件事情，一件是根据 ViewModel 中存储的状态渲染界面，另外一件是将用户操作转发给 ViewModel。用一个公式来表述是这样的：view = render(state) + handle(event)。</li>
<li>ViewModel 也只做两件事。一方面提供 observable properties 给 View 观察，一方面提供 functions 给 View 调用，通常会导致 observable properties 的改变，以及带来一些额外状态。observable properties 是指 LiveData、Observable(RxJava) 这种可观察属性。View 正是订阅了这些属性，实现模型驱动视图。functions 是指可以用来响应用户操作的方法或其它对象，ViewModel 不会也不应该自己处理业务，它通过 functions 把业务逻辑的处理委派给 Model。用一个公式来表述是这样的：viewmodel = observable properties + functions。 </li>
<li>Model 是整个应用的核心。它包含数据以及业务逻辑，网络访问、数据持久化都是它的职责。用一个公式来表述是这样的：model = data + state + business logic。 </li>
</ul>

<p>在 Android 中，View 包含 Activity 和 Fragment，由于 Activity 和 Fragment 可以销毁后重建，因此要求 ViewModel 在这个过程中能够存活下来，并绑定到新的 Activity 或 Fragment。架构组件提供了 <a href="https://developer.android.com/topic/libraries/architecture/viewmodel.html">ViewModel</a> 来帮我们实现这点。 </p>

<p>小伙伴们请注意区分，本文中的 ViewModel 可能是指 MVVM 中的 VM，也可能是指一个叫 ViewModel 的 Java 类。 </p>

<p>当 Activity 或 Fragment 真正 finish 的时候，框架会调用 <a href="https://developer.android.com/topic/libraries/architecture/viewmodel.html">ViewModel</a> 中的 <code>onCleared</code> 方法，我们需要在这个方法里面清除不再使用的资源。</p>

<p><img src="../images/android/viewmodel-lifecycle.png" alt=""> </p>

<p>官方实现让 ViewModel 在配置变化，譬如屏幕旋转，能够存活下来的方法，和我们前面说的 Lifecycle 无关，而是一种上古魔法。这种魔法自 Fragment 诞生以来就出现了，它的名字叫做 <a href="http://luboganev.github.io/blog/headless-fragments/">Headless Fragments</a>。</p>

<h3>RxCommand</h3>

<p>我们先前提及 ViewModel 提供 functions 给 View 调用，通常会导致 observable properties 的改变，以及带来一些额外状态。譬如，我们的 function 需要拉取一个用户列表，除了可能会导致表示用户列表的属性发生变化，在这个过程当中，还会出现一些额外状态，比如表示拉取是否正在执行的 executing，表示拉取可能发生异常的 errors，表示拉取动作是否可以执行的 enabled。 </p>

<p>如果这些额外状态也需要 ViewModel 去维护，那么 ViewModel 就无法专注于自身的核心业务了。</p>

<p><a href="https://github.com/listenzz/RxCommand">RxCommand</a> 一方面封装了这些 function，另一方管理了这些额外状态。 </p>

<p>使用 RxCommand 之前的代码可能是这样的： </p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public class MyViewModel extends ViewModel {
    private final UserRepository userRepository;

    // observable properties
    private final Observable&lt;List&lt;User&gt;&gt; users;

    private final Observable&lt;Boolean&gt; executing;
    private final Observable&lt;Boolean&gt; enabled;
    private final Observable&lt;Throwable&gt; errors;

    public MyViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
        users = PublishSubject.create();
        executing = PublishSubject.create();
        enabled = PublishSubject.create();
        errors = PublishSubject.create();
    }

    // function    
    public void loadUsers() {
        enabled.onNext(false);
        executing.onNext(true);

        userRepository
            .getUsers()
            .doOnNext(usersList -&gt; users.onNext(userList))
            .doOnError(throwable -&gt; errors.onNext(throwable))
            .doFinally(() -&gt; {
                executing.onNext(false);
                enabled.onNext(true);
            })
            .subscribe();                   
    }
}
</code></pre></figure>
<p>一大堆模版代码。如果 ViewModel 还负责加载其它东西，那场景不敢想象。</p>

<p>使用 <a href="https://github.com/listenzz/RxCommand">RxCommand</a> 的代码看起来是这样的： </p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public class MyViewModel extends ViewModel {

    // function
    public final RxCommand&lt;List&lt;User&gt;&gt; usersCommand;

    public MyViewModel(final UserRepository userRepository) {

        usersCommand = RxCommand.create(o -&gt; {
                return userRepository.getUsers();
            });
    }
}
</code></pre></figure>
<p>ViewModel 中的代码很清爽。在 Activity 中是这么用的：</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);

        model.usersCommand
                .switchToLatest()
                .observeOn(AndroidSchedulers.mainThread())
                .compose(Live.bindLifecycle(this))
                .subscribe(users -&gt; {
                    // update UI
                });

        model.usersCommand
                .executing()
                .compose(Live.bindLifecycle(this))
                .subscribe(executing -&gt; {
                    // show or hide loading
                })

        model.usersCommand
                .errors()
                .compose(Live.bindLifecycle(this))
                .subcribe(throwable -&gt; {
                    // show error message
                });
    }
}
</code></pre></figure>
<p>如果 ViewModel 中有多个 command 的话，可以通过 RxJava 的 merge 操作符统一处理 executing 和 error。</p>

<p>在代码中，我们看到了 <a href="https://github.com/listenzz/Live">Live</a>，它和 <a href="https://github.com/listenzz/RxCommand">RxCommand</a> 真是绝配。<strong>其实本文讲的就是 Live（Android 生命周期架构组件）和 RxCommand (RxJava) 协作，过上幸福生活的故事。</strong> </p>

<p>让我们来看下 <a href="https://github.com/listenzz/RxCommand">RxCommand</a> 附带的 demo。 </p>

<video width="360" height="640" controls>
<source src="/videos/rxcommand.mp4">
</video>

<p>很普遍但不简单的需求。这里面有两个输入框，以及两个按钮。当手机号码输入合法时，获取验证码的按钮才会被点亮。一旦获取验证码的操作正在执行以及执行成功后开始倒数，获取验证码按钮一直处于不可点击状态，当获取验证码失败或者倒数结束后，获取验证码按钮才恢复可点状态。只有手机号码和验证码输入都合法时，登录按钮才会被点亮。 </p>

<p><strong>尤其是，当屏幕旋转时，所有的状态和操作都保留着，当 Activiy 重新创建时，绑定到新的 Activity。</strong>而所有这些，都是 <a href="https://github.com/listenzz/Live">Live</a> 和 <a href="https://github.com/listenzz/RxCommand">RxCommand</a> 以及 <a href="https://developer.android.com/topic/libraries/architecture/viewmodel.html">ViewModel</a> 共同协作的结果。</p>

<p>如果你想去看 <a href="https://github.com/listenzz/RxCommand">源码</a>，推荐使用 Octotree 这个 Chrome 插件，可以直接在浏览器上看。如果你喜欢，clone 下来改改跑跑也是不错的选择。 </p>

<p>我们来分析下源码吧。 </p>

<p>ViewModel 被设计用来存储和管理 UI 相关的数据，在我们的 LoginViewModel 中，<code>phoneNumber</code> 和 <code>captcha</code> 被用来保存用户输入的手机号码以及验证码。 </p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public class LoginViewModel extends ViewModel{
    public final Variable&lt;CharSequence&gt; phoneNumber;
    public final Variable&lt;CharSequence&gt; captcha;

    public LoginViewModel() {
        phoneNumber = new Variable&lt;&gt;(&quot;&quot;);
        captcha = new Variable&lt;&gt;(&quot;&quot;);
    }
}
</code></pre></figure><figure class="highlight"><pre><code class="language-java" data-lang="java">public class LoginActivity extends AppCompatActivity implements LifecycleRegistryOwner{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        RxTextView
                .textChanges(phoneNumberEditText)
                .compose(Live.bindLifecycle(this))
                .subscribe(viewModel.phoneNumber::setValue);

       RxTextView
                .textChanges(captchaEditText)
                .compose(Live.bindLifecycle(this))
                .subscribe(viewModel.captcha::setValue);
    }
}
</code></pre></figure>
<p>这样，我们就接收了用户输入。</p>

<p>RxTextView 是 JakeWharton 开源的 <a href="https://github.com/JakeWharton/RxBinding">RxBinding</a> 中的组件。用于将 Android UI 控件带入流的世界。</p>

<p>Variable 是对 BehaviorSubject 的封装。</p>

<p>我们是用纯 java 的方式实现数据绑定，而不是使用 <a href="https://developer.android.com/topic/libraries/data-binding/index.html">data-binding</a> 这样的库。</p>

<p>当点击按钮时，会触发 ViewModel 中对应的 function，我们分别为获取验证码按钮和登录按钮定义了相应的 command。 我们来看看登录按钮相关的 command 在 ViewModel 中是如何定义的：</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public class LoginViewModel extends ViewModel{
    private RxCommand&lt;Boolean&gt; _loginCommand;

    // 用于检测手机号码或验证码是否合法
    private Observable&lt;Boolean&gt; _captchaValid;
    private Observable&lt;Boolean&gt; _phoneNumberValid;

    public LoginViewModel() {
         // 这里只是简单判断用户输入的长度
        _captchaValid = captcha.asObservable().map(s -&gt; s.toString().trim().length() == 6);
        _phoneNumberValid = phoneNumber.asObservable().map(s -&gt; s.toString().trim().length() == 11);
    }

    public RxCommand&lt;Boolean&gt; loginCommand() {
        if (_loginCommand == null) {

            // 因为登录按钮需要手机号码和验证码都合法时才能点亮，因此把它们合成一个流
            Observable&lt;Boolean&gt; loginInputValid = Observable.combineLatest(
                    _captchaValid,
                    _phoneNumberValid,
                    (captchaValid, phoneValid) -&gt; captchaValid &amp;&amp; phoneValid);

            // 第一个参数是一个发射 boolean 的流，
            // 当发射的值为 true 时，command 处于可执行状态，反之则不然，
            // 这个参数可以为 null，表示 command 是否可以执行不受外界影响。
            // 第二个参数是个 lambda，它接受一个参数 o，这个参数是 command 被执行时传入。
            // lambda 返回一个 Observable，通常直接返回业务层调用的结果
            _loginCommand = RxCommand.create(loginInputValid, o -&gt; {
                String phone = this.phoneNumber.value().toString();
                String captcha = this.captcha.value().toString();
                return login(phone, captcha);
            });
        }
        return _loginCommand;
    }

    private Observable&lt;Boolean&gt; login(String phoneNumber, String code) {
          // ...
    }
}
</code></pre></figure>
<p>我们来看看在 Activity 中如何使用：</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">RxCommandBinder
        .bind(loginButton, viewModel.loginCommand(), Live.bindLifecycle(this));
</code></pre></figure>
<p>这样就把登录按钮和 <code>loginCommand</code> 绑定了。当用户手机号码和验证码都合法时，登录按钮才处于可点击状态，当登录任务开始执行时，按钮将处于不可点击状态，直到登录任务结束，成功或失败，按钮才可再次处于可点击状态，如果此时输入还合法的话。 </p>

<p>当处于登录任务执行期间，想要显示个 loading ，怎么办呢？在 Activity 中：</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">viewModel.loginCommand()
        .executing()
        .compose(Live.bindLifecycle(this))
        .subscribe(executing -&gt; {
            if (executing) {
                // show loading UI
            } else {
                // hide loading UI
            }
        });
</code></pre></figure>
<p>就算屏幕旋转期间，仍然能正确展示。</p>

<p>让我们来看看获取验证码相关的 command，这里面的逻辑比较复杂。这里用到了一个隐藏的 command，它是用来负责倒计时的。</p>

<p>在 ViewModel 中</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">public RxCommand&lt;String&gt; captchaCommand() {
    if (_captchaCommand == null) {
         // 只有手机号码输入合法以及不在倒数时，获取验证码按钮才可点击
        Observable&lt;Boolean&gt; enabled = Observable.combineLatest(
                _phoneNumberValid,
                countdownCommand().executing(),
                (valid, executing) -&gt; valid &amp;&amp; !executing);

        _captchaCommand = RxCommand.create(enabled, o -&gt; {
            String phone = _phoneNumber.blockingFirst().toString();

            // 调用获取验证码的业务逻辑
            Observable fetchCode =  fetchCaptcha(phone);

            // 获取验证码成功后，应该开始倒数，
            // 这里，我们手动执行 countdown command
            Observable countdown =  Observable.defer(() -&gt; 
                        countdownCommand()
                            .execute(null)    // 手动执行 command
                            .ignoreElements() // 忽略返回值
                            .toObservable()
                    );
             // 利用 concat 操作符把获取验证码和倒计时的流串起来   
             // 获取验证码成功后，会开始倒计时，如果失败，则不会
            return Observable.concat(fetchCode, countdown);
        });
    }
    return _captchaCommand;
}

private Observable&lt;String&gt; fetchCaptcha(String phoneNumber) {
    return Observable.timer(2, TimeUnit.SECONDS)
            .map(i -&gt; &quot;your captcha is 123456.&quot;);
}

public RxCommand&lt;String&gt; countdownCommand() {
    if (_countdownCommand == null) {
    // 利用 RxJava 实现一个倒计时
    _countdownCommand = RxCommand.create(o -&gt; Observable
            .interval(1, TimeUnit.SECONDS)
            .take(20) // from 0 to 19
            .map(aLong -&gt; &quot;fetch &quot; + (19 - aLong) + &quot;&#39;&quot;));
    }
    return _countdownCommand;
}
</code></pre></figure>
<p>在 Activity 中 </p>
<figure class="highlight"><pre><code class="language-java" data-lang="java">// 绑定获取验证码按钮和 captchaCommand
RxCommandBinder
        .bind(captchaButton, viewModel.captchaCommand(), Live.bindLifecycle(this));

// 如果获取成功了，就通知用户                
viewModel.captchaCommand()
        .switchToLatest()
        .observeOn(AndroidSchedulers.mainThread())
        .compose(Live.bindLifecycle(this))
        .subscribe(result -&gt; Toast.makeText(LoginActivity.this, result, Toast.LENGTH_LONG).show()); 

 // 倒计时开始时，改变获取验证码按钮上的文字       
 viewModel.countdownCommand()
        .switchToLatest()
        .observeOn(AndroidSchedulers.mainThread())
        .compose(Live.bindLifecycle(this))
        .subscribe(s -&gt; captchaButton.setText(s));

// 获取验证码成功、失败或倒计时结束，都会重置获取验证码按钮的文字
viewModel.captchaCommand()
        .executing()
        .compose(Live.bindLifecycle(this))
        .subscribe(executing -&gt; {
            if (executing) {
                captchaButton.setText(&quot;Fetch...&quot;);
            } else {
                captchaButton.setText(&quot;Fetch Captcha&quot;);
            }
        });  
</code></pre></figure>
<p>Activity 和 ViewModel 中的代码加起来 250 行左右，就能实现你在视频中看到的效果。</p>

<p>RxCommand 不一定需要通过 RxCommandBinder 把它和某个 View（按钮）绑定起来，也可以手动执行，比如在下拉刷新控件的回调中直接调用 <code>command.execute(null)</code>。</p>

<h3>MVVM 与 展示层</h3>

<p>MVVM 架构放在一个更大的架构范畴中，只是一个展示层的架构，只是解决了 View 和 ViewModel 之间的关系。然而，在一个应用中，Model 才是核心。标准的三层架构和整洁架构，都将焦点放在 Model 身上。只懂 MVVM 是处理不好的业务的。这意味着，我们还有很长的路要走，还有许多未知领域需要探索。</p>

<h2>总结</h2>

<p>本文中，我们介绍了 Android <a href="https://developer.android.com/topic/libraries/architecture/lifecycle.html">生命周期架构组件</a> 、<a href="https://github.com/listenzz/Live">Live</a> 、 <a href="https://developer.android.com/topic/libraries/architecture/viewmodel.html">ViewModel</a> 、<a href="https://github.com/listenzz/RxCommand">RxCommand</a>。</p>

<p>愿生活更美好！</p>

<p><img src="../images/android/happy-quotes.png" alt="">  </p>


    </section>

    <footer class="post-footer">

      <!-- Everything inside the #author tags pulls data from the author -->
      <!-- #author-->

      
      <figure class="author-image">
        <a class="img" href='/' style="background-image: url(/assets/images/me.jpg)"><span class="hidden">'s Picture</span></a>
      </figure>
      

      <section class="author">
        <h4><a href='/'>Listen</a></h4>

        
        <p> 移动开发工程师，熟悉 Android、iOS、ReactNative</p>
        
        <div class="author-meta">
          <span class="author-location icon-location"> 深圳</span>
          <span class="author-link icon-link"><a href="https://github.com/listenzz"> Github</a></span>
        </div>
      </section>

      <!-- /author  -->

      <section class="share">
        <h4>Share this post</h4>
        <a class="icon-twitter" href="http://twitter.com/share?text=Android 生命周期架构组件与 RxJava 完美协作&amp;url=https://github.com/listenzzandroid-lifecyle-works-perfectly-with-rxjava.html"
          onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
          <span class="hidden">Twitter</span>
        </a>
        <a class="icon-facebook" href="https://www.facebook.com/sharer/sharer.php?u=https://github.com/listenzzandroid-lifecyle-works-perfectly-with-rxjava.html"
          onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;">
          <span class="hidden">Facebook</span>
        </a>
        <a class="icon-google-plus" href="https://plus.google.com/share?url=https://github.com/listenzzandroid-lifecyle-works-perfectly-with-rxjava.html"
          onclick="window.open(this.href, 'google-plus-share', 'width=490,height=530');return false;">
          <span class="hidden">Google+</span>
        </a>
      </section>

      <!-- Add Disqus Comments -->
      <!-- <section class="disqus">
    <div id="disqus_thread"></div>
    <script type="text/javascript">
        var disqus_shortname  = 'listenzz';
        var disqus_identifier = 'Android 生命周期架构组件与 RxJava 完美协作';
        var disqus_title      = "Android 生命周期架构组件与 RxJava 完美协作";
        
        (function() {
         var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
         dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
         (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
         })();
        </script>
    <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
    <a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
</section>
 -->

    </footer>

  </article>

</main>

<aside class="read-next">

  <!-- [[! next_post ]] -->
  
  <a class="read-next-story no-cover" href="/how-to-enable-k8s-from-docker-desktop.html">
    <section class="post">
      <h2>在 macOS 上使用 Docker Desktop 启动 Kubernetes</h2>
      <p>在中国做程序员真难，经过几天的折腾，终于成功在 macOS 上使用 Docker Desktop 启动 Kubernetes，特此记录。 ## 为 Docker for Mac 配置 HTTP 代理...</p>
    </section>
  </a>
  
  <!-- [[! /next_post ]] -->
  <!-- [[! prev_post ]] -->
  
  <a class="read-next-story prev no-cover" href="/android-architecture-part-4-applying-clean-architecture-on-android-hands-on.html">
    <section class="post">
      <h2>[译] Android 架构：Part 4 —— 实践 Clean Architecture（含源码）</h2>
      <p>在 Android 架构系列的最后部分，我们将 Clean Architecture 调整到 Android 平台。我们将 Android 和真实世界从业务逻辑中分离，令利益相关者满意，使一切都容易测试。 理论很棒，但是当我们创建一个新 Android 项目时，该从哪开始呢？让我们用整洁代码弄脏我们的手，把空白画布变成一个架构。 基础 让我们先做一些基础工作...</p>
    </section>
  </a>
  
  <!-- [[! /prev_post ]] -->
</aside>

<!-- /post -->


      <footer class="site-footer clearfix">
        <section class="copyright"><a href="/">在编码</a> &copy; 2020</section>
        <section class="poweredby">Proudly published with <a href="https://jekyllrb.com/">Jekyll</a> using <a href="https://github.com/biomadeira/jasper">Jasper</a></section>
      </footer>
    </div>
    <!-- [[! Ghost outputs important scripts and data with this tag ]] -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
    <!-- [[! The main JavaScript file for Casper ]] -->
    <script type="text/javascript" src="/assets/js/jquery.fitvids.js"></script>
    <script type="text/javascript" src="/assets/js/index.js"></script>
    <script type="text/javascript" src="/assets/js/prism.js"></script>

    <!-- Add Google Analytics  -->
    <!-- Google Analytics Tracking code -->
<script>
 (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
   (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
   m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
 })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

 ga('create', 'UA-96120107-1', 'auto');
 ga('send', 'pageview');

</script>

  </body>
</html>
